import amara,re,os

class s_record:
    """
    A class to store help store information about the text substrings
    to amara object mappings    
    """
    def __init__(self, xmlObj, child_num, interval):
        """
        xmlObj is a reference to a xml object in Amara. interval is
        the string interval (a,b), that represents the end points of
        the text that belongs in xmlObj.xml_children[child_num]
        """
        self.obj = xmlObj
        self.interval = interval
        self.child_num = child_num
        return        

    def printit(self):
        """
        Debug info
        """
        print "{ obj = *", self.obj.xml_children[self.child_num],"* child number: ", self.child_num, " interval: ", self.interval, "}"
        return
    
    def __cmp__(self, other):
        """
        Used in sorting.
        """
        #print "called: ", self.interval, other.interval, self.interval < other.interval
        return cmp(self.interval,other.interval)
         

class TaxonX:
    """
    Methods to parse, databind and return text between tags in a
    taxonx document.

    TODO: Memory enhancements may be possible, using Pull parsing.
    """

    def __init__(self, filename):
        self.filename = filename
        self.doc = amara.parse(self.filename)

        self.build_stringdata(self.doc.taxonx.taxonxBody)
        #self.build_stringdata(self.doc)
        return

    
    def build_stringdata(self,node):
        """
        Builds internal mapping between amara objects and strings in
        the text, using a recursive function "get_next_string()"
        """
        self.xmldata = u"" #all text in the xml document
        self.taginfo = []
        self.stack = [[node,0]]

        while len(self.stack)>0:
            self.get_next_string()
            #count += 1
            #print len(self.taginfo),self.taginfo[-1].printit()

        return

    def debug(self):
        print "OUTPUTTING self.taginfo"
        count = 0
        for each in self.taginfo:
            print each.printit()
            count += 1
            if count > 10: 
                break
        return

    def get_next_string(self):
        """
        Recursive function that traverses the xml root object "doc"
        returned by the amara parser. Each call finds exactly one
        unicode object, namely the string that occurs serially (in the
        XML document) after the one returned by the previous call to
        this function.
        """

        if len(self.stack)==0:
            return 

        c_node = self.stack[-1][0]
        c_index = self.stack[-1][1]
        if (c_index == len(c_node.xml_children)):
            self.stack.pop()
            #print "got to end of stack. popping."
            self.get_next_string()
            return

        if (type(c_node.xml_children[c_index])==unicode):
            st = len(self.xmldata)
            end = len(self.xmldata)+len(c_node.xml_children[c_index])-1
            srec = s_record(c_node, c_index, [st,end])
            self.taginfo += [srec]
            
            self.xmldata += c_node.xml_children[c_index]
            #increment element.
            self.stack[-1][1] += 1
            #print "got a string.returning"
        else:
            self.stack[-1][1] += 1
            self.stack += [[c_node.xml_children[c_index],0]]
            #print "got another xmlobj."
            self.get_next_string()

        return


    def get_index(self, N):
        """
        Does a binary search, in the self.taginfo list to find the
        object in which text at index N is included. self.taginfo is a
        list of s_record objects.
        """
        lo,hi = 0,len(self.taginfo)
        while (hi-lo>1):
            mid = (lo+hi)/2
            if (N>=self.taginfo[mid].interval[0]):
                lo = mid
            else:
                hi = mid
        if N>=self.taginfo[lo].interval[0] and N<=self.taginfo[lo].interval[1]:
            return lo
        else:
            return hi

    def fix_other_children(self, obj, x, offset):
        """
        Shifts xml children in xml object "obj" by "offset", if the
        child is initially at an index greater than x.
        """
        if offset==0: return
        for each in self.taginfo: 
            if (each.obj == obj and each.child_num>x):
                each.child_num += offset 
        return


    def insert_new_tag(self, interval, tagname):
        """
        Inserts a new tag into the XML document. The text substring
        (interval[0], interval[1]), is placed between a tag named
        "tagname".
        """
        st = self.get_index(interval[0])
        end = self.get_index(interval[1])
        child_num = self.taginfo[st].child_num
        #check if the locations are ok, ie no overlapping, invalid
        #nesting, etc.
        if st != end:
            raise InvalidTagPosError, "Invalid insert position for tag."
        elif self.taginfo[st].obj.localName == tagname:
            raise AlreadyExists, "Cannot nest same tag"
        
        #construct the new nodes in the xml tree
        n_st = self.taginfo[st].interval[0]
        n_end = self.taginfo[st].interval[1]
        our_st, our_end = interval[0], interval[1]

        #print "xmldata: *"+self.xmldata[n_st: n_end+1]+"*"

        prefix = self.xmldata[n_st:our_st]
        middle = self.xmldata[our_st:our_end+1]
        suffix = self.xmldata[our_end+1:n_end+1]

        #print "\""+prefix+"\",\""+middle+"\",\""+suffix+ "\""

        obj = self.taginfo[st].obj
        fix_count = 0
        if suffix!="": fix_count += 1
        if prefix!="": fix_count += 1
        self.fix_other_children(obj, child_num, fix_count)
        del_child_num = child_num
        #print "fix_count: ", fix_count
        
        obj.xml_insert_after(obj.xml_children[child_num], obj.xml_create_element(tagname))
        obj.xml_children[child_num+1].xml_append(middle)
        srec = s_record(obj.xml_children[child_num+1], 0, [our_st,our_end])
        self.taginfo.append(srec)

        obj.xml_remove_child_at(del_child_num)
        del self.taginfo[st]

        added=0

        if prefix!="": 
            obj.xml_insert_before(obj.xml_children[child_num],prefix)
            srec = s_record(obj, child_num, [n_st,our_st-1])
            self.taginfo.append(srec)
            added += 1
            

        if suffix!="":
            obj.xml_insert_after(obj.xml_children[child_num+added], suffix)
            srec = s_record(obj, child_num+2, [our_end+1,n_end])
            self.taginfo.append(srec)

        self.taginfo.sort()
        return



if __name__ == "__main__":
    """
    Testing code. Will not be executed when this file is imported.
    """
    
    filename = "../data/xmldocs/10342_tx1.xml"
    #filename = "example.xml"
    
    T = TaxonX(filename)
    print T.doc.taxonx.taxonxBody.xml()[0:1000]
    s = T.xmldata
    print s[10:35]
    #T.debug()
    T.insert_new_tag([10,34], u"sometext")
    print T.doc.taxonx.taxonxBody.xml()[0:1000]
    #T.debug()
    T.insert_new_tag([36,40], u"anothertalk")
    #T.debug()
    print T.doc.taxonx.taxonxBody.xml()[0:1000]
    
